using System;
using System.Collections;
#if SILVERLIGHT
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Shapes;
using System.ComponentModel;
#else
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Input;
using System.Collections.Generic;
#endif

namespace Microsoft.PlayerFramework
{
    [TemplatePart(Name = SeekableSliderTemplateParts.HorizontalTemplate, Type = typeof(FrameworkElement))]
    [TemplatePart(Name = SeekableSliderTemplateParts.HorizontalThumb, Type = typeof(Thumb))]
    [TemplatePart(Name = SeekableSliderTemplateParts.HorizontalAvailableBar, Type = typeof(FrameworkElement))]
    [TemplatePart(Name = SeekableSliderTemplateParts.HorizontalPositionedItemsControl, Type = typeof(ItemsControl))]
    [TemplatePart(Name = SeekableSliderTemplateParts.VerticalTemplate, Type = typeof(FrameworkElement))]
    [TemplatePart(Name = SeekableSliderTemplateParts.VerticalThumb, Type = typeof(Thumb))]
    [TemplatePart(Name = SeekableSliderTemplateParts.VerticalAvailableBar, Type = typeof(FrameworkElement))]
    [TemplateVisualState(Name = TimelineVisualStates.ScrubbingStates.IsScrubbing, GroupName = TimelineVisualStates.GroupNames.ScrubbingStates)]
    [TemplateVisualState(Name = TimelineVisualStates.ScrubbingStates.IsNotScrubbing, GroupName = TimelineVisualStates.GroupNames.ScrubbingStates)]
    public partial class SeekableSlider
    {
        // template controls
        /// <summary>
        /// The bar used to restrict the available area that can be scrubbed to.
        /// </summary>
        protected FrameworkElement AvailableBar { get; private set; }

        /// <summary>
        /// The Panel used to host the control.
        /// </summary>
        protected FrameworkElement Panel { get; private set; }

        /// <summary>
        /// The thumb used to allow the user to seek.
        /// </summary>
        protected Thumb Thumb { get; private set; }

        #region Template Children

        /// <inheritdoc /> 
#if SILVERLIGHT
        public override void OnApplyTemplate()
#else
        protected override void OnApplyTemplate()
#endif
        {
            base.OnApplyTemplate();
            UninitializeTemplateChildren();
            GetTemplateChildren();
            InitializeTemplateChildren();
        }

        private void GetTemplateChildren()
        {
            if (Orientation == Orientation.Horizontal)
            {
                Panel = GetTemplateChild(SeekableSliderTemplateParts.HorizontalTemplate) as FrameworkElement;
                Thumb = GetTemplateChild(SeekableSliderTemplateParts.HorizontalThumb) as Thumb;
                AvailableBar = GetTemplateChild(SeekableSliderTemplateParts.HorizontalAvailableBar) as FrameworkElement;
            }
            else
            {
                Panel = GetTemplateChild(SeekableSliderTemplateParts.VerticalTemplate) as FrameworkElement;
                Thumb = GetTemplateChild(SeekableSliderTemplateParts.VerticalThumb) as Thumb;
                AvailableBar = GetTemplateChild(SeekableSliderTemplateParts.VerticalAvailableBar) as FrameworkElement;
            }
        }

        private void InitializeTemplateChildren()
        {
            if (AvailableBar != null)
            {
#if SILVERLIGHT
                AvailableBar.MouseLeftButtonDown += bar_PointerPressed;
                AvailableBar.MouseLeftButtonUp += bar_PointerReleased;
                AvailableBar.MouseMove += bar_PointerMoved;
#else
                AvailableBar.PointerPressed += bar_PointerPressed;
                AvailableBar.PointerReleased += bar_PointerReleased;
                AvailableBar.PointerMoved += bar_PointerMoved;
#endif
            }
            if (Panel != null)
            {
                Panel.SizeChanged += PanelSizeChanged;
            }

            if (Thumb != null)
            {
                Thumb.DragStarted += ThumbDragStarted;
                Thumb.DragDelta += ThumbDragDelta;
                Thumb.DragCompleted += ThumbDragCompleted;
            }
        }

        private void UninitializeTemplateChildren()
        {
            // main container
            if (AvailableBar != null)
            {
#if SILVERLIGHT
                AvailableBar.MouseLeftButtonDown -= bar_PointerPressed;
                AvailableBar.MouseLeftButtonUp -= bar_PointerReleased;
                AvailableBar.MouseMove -= bar_PointerMoved;
#else
                AvailableBar.PointerPressed -= bar_PointerPressed;
                AvailableBar.PointerReleased -= bar_PointerReleased;
                AvailableBar.PointerMoved -= bar_PointerMoved;
#endif
            }
            if (Panel != null)
            {
                Panel.SizeChanged -= PanelSizeChanged;
            }

            // thumb
            if (Thumb != null)
            {
                Thumb.DragStarted -= ThumbDragStarted;
                Thumb.DragDelta -= ThumbDragDelta;
                Thumb.DragCompleted -= ThumbDragCompleted;
            }
        }

        #endregion

        #region Dependency Properties

        #region MarkerTemplate
        /// <summary>
        /// MarkerTemplate DependencyProperty definition.
        /// </summary>
        public static readonly DependencyProperty MarkerTemplateProperty = DependencyProperty.Register("MarkerTemplate", typeof(DataTemplate), typeof(SeekableSlider), new PropertyMetadata(null, (d, e) => ((SeekableSlider)d).OnMarkerTemplateChanged(e.NewValue as DataTemplate)));

        void OnMarkerTemplateChanged(DataTemplate newValue)
        {
        }

        /// <summary>
        /// Gets or sets the actual value of the slider to be able to maintain the value of the slider while the user is scrubbing.
        /// </summary>
        public DataTemplate MarkerTemplate
        {
            get { return (DataTemplate)GetValue(MarkerTemplateProperty); }
            set { SetValue(MarkerTemplateProperty, value); }
        }
        #endregion

        #region Markers
#if SILVERLIGHT
        /// <summary>
        /// Markers DependencyProperty definition.
        /// </summary>
        public static readonly DependencyProperty MarkersProperty = DependencyProperty.Register("Markers", typeof(IEnumerable), typeof(SeekableSlider), null);

        /// <summary>
        /// Gets or sets the actual value of the slider to be able to maintain the value of the slider while the user is scrubbing.
        /// </summary>
        public IEnumerable Markers
        {
            get { return GetValue(MarkersProperty) as IEnumerable; }
            set { SetValue(MarkersProperty, value); }
        }

#else
        // TODO: Bug in Win8 doesn't allow us to bind to IEnumerable. Remove when fixed.
        /// <summary>
        /// Markers DependencyProperty definition.
        /// </summary>
        public static readonly DependencyProperty MarkersProperty = DependencyProperty.Register("Markers", typeof(object), typeof(SeekableSlider), null);

        /// <summary>
        /// Gets or sets the actual value of the slider to be able to maintain the value of the slider while the user is scrubbing.
        /// </summary>
        public object Markers
        {
            get { return GetValue(MarkersProperty); }
            set { SetValue(MarkersProperty, value); }
        }

#endif
        #endregion

        #region ActualValue
        /// <summary>
        /// ActualValue DependencyProperty definition.
        /// </summary>
        public static readonly DependencyProperty ActualValueProperty = DependencyProperty.Register("ActualValue", typeof(double), typeof(SeekableSlider), new PropertyMetadata(0.0, (d, e) => ((SeekableSlider)d).OnActualValueChanged((double)e.NewValue)));

        void OnActualValueChanged(double newValue)
        {
            if (!IsScrubbing)
            {
                Value = newValue;
            }
        }

        /// <summary>
        /// Gets or sets the actual value of the slider to be able to maintain the value of the slider while the user is scrubbing.
        /// </summary>
        public double ActualValue
        {
            get { return (double)GetValue(ActualValueProperty); }
            set { SetValue(ActualValueProperty, value); }
        }

        #endregion

        #region MaxValue
        /// <summary>
        /// MaxValue DependencyProperty definition.
        /// </summary>
        public static readonly DependencyProperty MaxValueProperty = DependencyProperty.Register("MaxValue", typeof(double), typeof(SeekableSlider), new PropertyMetadata(double.NaN, (d, e) => ((SeekableSlider)d).OnMaxValueChanged()));

        void OnMaxValueChanged()
        {
            RestrictAvailability();
        }

        /// <summary>
        /// Gets or sets the max position of the timeline.
        /// </summary>
        public double MaxValue
        {
            get { return (double)GetValue(MaxValueProperty); }
            set { SetValue(MaxValueProperty, value); }
        }

        double Max
        {
            get
            {
                return double.IsNaN(MaxValue) ? Maximum : MaxValue;
            }
        }

        #endregion

        #endregion

        #region Event Handlers

        private void PanelSizeChanged(object sender, SizeChangedEventArgs e)
        {
            // bar size changed, update available bar
            RestrictAvailability();
        }

        private void ThumbDragStarted(object sender, DragStartedEventArgs e)
        {
            UpdateScrubbingVisualState();
            if (ScrubbingStarted != null)
            {
                ScrubbingStarted(this, new ValueRoutedEventArgs(Value));
            }
        }

        private void ThumbDragDelta(object sender, DragDeltaEventArgs e)
        {
            if (Value > Max) Value = Max;
            if (Scrubbing != null)
            {
                Scrubbing(this, new ValueRoutedEventArgs(Value));
            }
        }

        private void ThumbDragCompleted(object sender, DragCompletedEventArgs e)
        {
            if (!e.Canceled)
            {
                if (Value > Max) Value = Max;
                UpdateScrubbingVisualState();
                if (ScrubbingCompleted != null)
                {
                    ScrubbingCompleted(this, new ValueRoutedEventArgs(Value));
                }
            }
        }

#if SILVERLIGHT
        private void bar_PointerPressed(object sender, MouseButtonEventArgs e)
#else
        private void bar_PointerPressed(object sender, PointerRoutedEventArgs e)
#endif
        {
#if SILVERLIGHT
            pointerCaptured = ((FrameworkElement)sender).CaptureMouse();
#else
            pointerCaptured = ((FrameworkElement)sender).CapturePointer(e.Pointer);
#endif
            UpdateScrubbingVisualState();
            if (pointerCaptured)
            {
#if SILVERLIGHT
                pointerReleaseAction = () => ((FrameworkElement)sender).ReleaseMouseCapture();
#else
                pointerReleaseAction = () => ((FrameworkElement)sender).ReleasePointerCapture(e.Pointer);
#endif
                double? newValue = null;
                if (Orientation == Orientation.Horizontal) newValue = GetHorizontalPanelMousePosition(e);
                else newValue = GetVerticalPanelMousePosition(e);

                if (newValue.HasValue)
                {
                    var value = Math.Min(newValue.Value, Max);
                    if (ScrubbingStarted != null)
                    {
                        ScrubbingStarted(this, new ValueRoutedEventArgs(value));
                    }
                    Value = value;
                }
            }
        }

#if SILVERLIGHT
        private void bar_PointerReleased(object sender, MouseButtonEventArgs e)
#else
        private void bar_PointerReleased(object sender, PointerRoutedEventArgs e)
#endif
        {
            if (pointerCaptured)
            {
                pointerReleaseAction();
                pointerReleaseAction = null;
                pointerCaptured = false;

                UpdateScrubbingVisualState();
                double? newValue = null;
                if (Orientation == Orientation.Horizontal) newValue = GetHorizontalPanelMousePosition(e);
                else newValue = GetVerticalPanelMousePosition(e);

                if (newValue.HasValue)
                {
                    Value = Math.Min(newValue.Value, Max);
                    if (ScrubbingCompleted != null)
                    {
                        ScrubbingCompleted(this, new ValueRoutedEventArgs(Value));
                    }
                }
            }
        }

#if SILVERLIGHT
        private void bar_PointerMoved(object sender, MouseEventArgs e)
#else
        private void bar_PointerMoved(object sender, PointerRoutedEventArgs e)
#endif
        {
            if (pointerCaptured)
            {
                double? newValue = null;
                if (Orientation == Orientation.Horizontal) newValue = GetHorizontalPanelMousePosition(e);
                else newValue = GetVerticalPanelMousePosition(e);

                if (newValue.HasValue)
                {
                    Value = Math.Min(newValue.Value, Max);
                    if (Scrubbing != null)
                    {
                        Scrubbing(this, new ValueRoutedEventArgs(Value));
                    }
                }
            }
        }

        #endregion

        #region Misc
        private void UpdateScrubbingVisualState()
        {
            var state = IsScrubbing ? TimelineVisualStates.ScrubbingStates.IsScrubbing : TimelineVisualStates.ScrubbingStates.IsNotScrubbing;
            this.GoToVisualState(state);
        }

        /// <summary>
        /// Cancels the active scrub
        /// </summary>
        public void CancelScrub()
        {
            if (pointerCaptured)
            {
                pointerReleaseAction();
                pointerReleaseAction = null;
                pointerCaptured = false;
                UpdateScrubbingVisualState();
            }
            else if (Thumb.IsDragging)
            {
                Thumb.CancelDrag();
            }
        }
        #endregion
    }
}